use actix_web::Responder;
use serde::{Deserialize, Serialize};
use wither::mongodb::bson::Document;
use wither::mongodb::options::FindOptions;

mod serde_default {
    pub fn pagination_size() -> isize {
        10
    }
    pub fn pagination_page() -> usize {
        0
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationQuery {
    #[serde(default = "serde_default::pagination_size")]
    pub size: isize,
    #[serde(default = "serde_default::pagination_page")]
    pub page: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sort_items: Option<Vec<(String, bool)>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sort: Option<String>,
}

impl Default for PaginationQuery {
    fn default() -> Self {
        Self {
            size: 10,
            page: 0,
            sort: None,
            sort_items: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationInfo {
    pub current_page: usize,
    pub current_size: isize,
    pub has_next_page: bool,
    pub has_prev_page: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pagination<T> {
    pub items: Vec<T>,
    pub page_info: PaginationInfo,
    pub total_count: usize,
}

impl<T> Pagination<T> {
    #[inline]
    pub fn map<F, P>(self, f: F) -> Pagination<P>
    where
        F: FnMut(&T) -> P,
    {
        let n_item = self.items.iter().map(f).collect::<Vec<_>>();
        Pagination::<P> {
            items: n_item,
            page_info: self.page_info,

            total_count: self.total_count,
        }
    }

    #[inline]
    pub fn map_into<P>(self) -> Pagination<P>
    where
        T: Into<P>,
        T: Clone,
    {
        self.map(|f| f.clone().into())
    }
}

fn str_to_sort_array(string: &str) -> Vec<(String, bool)> {
    string
        .split(',')
        .filter_map(|v| {
            if v.is_empty() {
                return None;
            }
            if v.len() == 1 {
                return Some((v.to_string(), true));
            }
            let (sort_str, key) = v.split_at(1);
            let sort = match sort_str {
                "+" => true,
                "-" => false,
                _ => return Some((v.to_string(), true)),
            };
            Some((key.to_string(), sort))
        })
        .collect::<Vec<_>>()
}
impl From<PaginationQuery> for Option<FindOptions> {
    fn from(p: PaginationQuery) -> Self {
        let size = p.size;
        let skip = p.size as usize * p.page;
        let (sort_map, sort_str) = (p.sort_items, p.sort);
        let sort = sort_map.unwrap_or_else(|| {
            sort_str
                .map(|v| str_to_sort_array(v.as_str()))
                .unwrap_or_default()
        });
        let mut sort_doc = Document::new();
        for (key, value) in sort {
            sort_doc.insert(key, if value { 1 } else { -1 });
        }
        Some(
            FindOptions::builder()
                .limit(Some(size as i64))
                .skip(Some(skip as u64))
                .sort(Some(sort_doc))
                .build(),
        )
    }
}

impl<T> Responder for Pagination<T>
where
    T: Serialize,
{
    type Body = actix_web::body::BoxBody;
    fn respond_to(self, _req: &actix_web::HttpRequest) -> actix_web::HttpResponse {
        actix_web::HttpResponse::Ok().json(self)
    }
}

/// 数组转分页
pub trait ToPagination<T> {
    /// 对数组进行分页操作
    fn to_pagination(&self, query: &PaginationQuery) -> Pagination<T>;
    /// 不进行分页操作，只是创建分页结构体
    fn new_pagination(
        &self,
        query: &PaginationQuery,
        has_next_page: bool,
        has_prev_page: bool,
    ) -> Pagination<T>;
    fn new_pagination_with_total_count(
        &self,
        query: &PaginationQuery,
        has_next_page: bool,
        has_prev_page: bool,
        total_count: usize,
    ) -> Pagination<T> {
        let mut page = self.new_pagination(query, has_next_page, has_prev_page);
        page.total_count = total_count;
        page
    }
}
impl<T: Clone> ToPagination<T> for Vec<T> {
    fn to_pagination(&self, query: &PaginationQuery) -> Pagination<T> {
        self.as_slice().to_pagination(query)
    }

    fn new_pagination(
        &self,
        query: &PaginationQuery,
        has_next_page: bool,
        has_prev_page: bool,
    ) -> Pagination<T> {
        self.as_slice()
            .new_pagination(query, has_next_page, has_prev_page)
    }
}

impl<T: Clone> ToPagination<T> for &[T] {
    fn to_pagination(&self, query: &PaginationQuery) -> Pagination<T> {
        if query.size < 0 {
            return ToPagination::new_pagination(self, query, false, false);
        }
        let vec_self = *self;
        let mut has_next_page = true;
        let has_prev_page = query.page > 0;
        let begin = {
            let max_begin = (query.size as usize) * query.page;
            if max_begin > vec_self.len() {
                has_next_page = false;
                vec_self.len()
            } else {
                max_begin
            }
        };
        let end = {
            let max_end = begin + query.size as usize;
            if max_end > vec_self.len() {
                has_next_page = false;
                vec_self.len()
            } else {
                max_end
            }
        };
        Pagination {
            total_count: vec_self.len(),
            page_info: PaginationInfo {
                current_page: query.page,
                current_size: query.size,
                has_next_page,
                has_prev_page,
            },
            items: vec_self[begin..end].to_vec(),
        }
    }

    fn new_pagination(
        &self,
        query: &PaginationQuery,
        has_next_page: bool,
        has_prev_page: bool,
    ) -> Pagination<T> {
        Pagination {
            total_count: self.len(),
            page_info: PaginationInfo {
                current_page: query.page,
                current_size: query.size,
                has_next_page,
                has_prev_page,
            },
            items: self.to_vec(),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    pub fn test_str_to_sort_map() {
        let map = str_to_sort_array("+created_at,-updated_at,+user.id");
        assert!(map.iter().eq(vec![
            ("created_at".to_string(), true),
            ("updated_at".to_string(), false),
            ("user.id".to_string(), true)
        ]
        .iter()))
    }
}
